package com.nexmo.hangman.service.impl;

import com.nexmo.hangman.dao.GameDao;
import com.nexmo.hangman.domain.Game;
import com.nexmo.hangman.domain.GameState;
import com.nexmo.hangman.domain.dto.GameStatusDto;
import com.nexmo.hangman.domain.dto.GuessResultDto;
import com.nexmo.hangman.domain.dto.ManagementGameStatusDto;
import com.nexmo.hangman.service.GameProvider;
import com.nexmo.hangman.service.GameService;
import com.nexmo.hangman.service.utils.GameStatusDtoBuilder;
import com.nexmo.hangman.service.utils.Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Service
public class GameServiceImpl implements GameService {

    private final GameProvider gameProvider;
    private final GameDao gameDao;
    private final GameStatusDtoBuilder gameStatusDtoBuilder;

    @Autowired
    public GameServiceImpl(GameProvider gameProvider, GameDao gameDao, GameStatusDtoBuilder gameStatusDtoBuilder) {
        this.gameProvider = gameProvider;
        this.gameDao = gameDao;
        this.gameStatusDtoBuilder = gameStatusDtoBuilder;
    }

    @Override
    @Transactional
    public GameStatusDto generateGame(Long userId) {
        gameDao.finishAllUserGames(userId);
        Game newGame = gameProvider.generateGame(userId);
        gameDao.save(newGame);
        return gameStatusDtoBuilder.buildFrom(newGame);
    }

    @Override
    @Transactional
    public GuessResultDto guess(Character guessChar, Long gameId) {
        Game game = gameDao.findById(gameId);
        GameState currentState = game.getState();
        int livesRemaining = currentState.getLivesRemaining();
        GuessResultDto guessResultDto = new GuessResultDto();

        String introduced = currentState.getIntroduced();
        String wordToGuess = game.getWordToGuess();

        if (introduced.indexOf(guessChar) < 0) {
            currentState.setIntroduced(introduced + guessChar);
            boolean hasWon = checkWinCondition(wordToGuess, introduced, guessChar);
            if(hasWon) {
                guessResultDto.setWon(hasWon);
                game.setFinished(true);
            } else {

                List<Integer> positionsDiscovered = Utils.stringFindAll(wordToGuess, guessChar);

                if (!positionsDiscovered.isEmpty()) {
                    guessResultDto.setGuessedPositions(positionsDiscovered);
                } else {
                    livesRemaining--;
                    if (livesRemaining > 0) {
                        currentState.setLivesRemaining(livesRemaining);
                        guessResultDto.setLivesRemaining(livesRemaining);
                    } else {
                        guessResultDto.setLost(true);
                        guessResultDto.setHiddenWord(wordToGuess);
                        game.setFinished(true);
                    }
                }
            }
        }
        gameDao.saveOrUpdate(game);
        return guessResultDto;
    }

    private boolean checkWinCondition(String wordToGuess, String introducedChars, Character guessChar) {
        Set<Character> charsToGuessSet = new HashSet<Character>();
        Set<Character> charactersGuessed = new HashSet<Character>();
        for(int i = 0; i < wordToGuess.length(); i++) {
            charsToGuessSet.add(wordToGuess.charAt(i));
        }
        for(int i = 0; i < introducedChars.length(); i++) {
            charactersGuessed.add(introducedChars.charAt(i));
        }
        charsToGuessSet.removeAll(charactersGuessed);
        return charsToGuessSet.size() == 1 && guessChar == charsToGuessSet.iterator().next();
    }

    @Override
    @Transactional
    public GameStatusDto getUnfinishedGame(Long userId) {
        Game lastUnfinishedGame = gameDao.getUserUnfinishedGame(userId);
        return lastUnfinishedGame != null ? gameStatusDtoBuilder.buildFrom(lastUnfinishedGame) : null;
    }

    @Override
    @Transactional
    public List<ManagementGameStatusDto> getAllActiveGames() {
        List<Game> activeGames = gameDao.getAllActiveGames();
        return gameStatusDtoBuilder.buildManagementEntityFrom(activeGames);
    }
}
